package main

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"github.com/Knetic/govaluate"
	"github.com/olivere/elastic/v7"
	"net/http"
	"strconv"
	"sync"
	"time"
)

// 用于存储每个监控项的告警次数
// var alertCountPerMonitorItem = make(map[string]int)
var (
	rulesCache        = make(map[string]Rule)
	cacheMutex        sync.RWMutex
	activeAlertsCache = make(map[string]bool)
)

// AlertData 保存告警相关信息
type AlertData struct {
	FiringTime  string            `yaml:"firing_time"`
	ResolveTime string            `json:"ResolveTime,omitempty"`
	Alert       string            `yaml:"alert"`
	Annotations string            `yaml:"annotations"`
	Rule        map[string]string `yaml:"rule"`
	Status      bool              `yaml:"status"`
	LastNotice  string            `json:"LastNotice,omitempty"`
}

// Trigger 告警触发判断
func Trigger(fieldMap map[string]int64, triggerExpr string, alertName string) {
	log.Debugf("fieldMap: %v, triggerExpr: %s, alertName: %s", fieldMap, triggerExpr, alertName)

	parameters := make(map[string]interface{})
	for k, v := range fieldMap {
		parameters[k] = float64(v)
	}

	cacheMutex.RLock()
	defer cacheMutex.RUnlock()

	found := false
	for _, rule := range rulesCache {
		if rule.Alert == alertName {
			found = true
			expression, err := govaluate.NewEvaluableExpression(triggerExpr)
			if err != nil {
				log.Errorf("无法解析表达式: %v", err)
				return
			}

			result, err := expression.Evaluate(parameters)
			if err != nil {
				log.Errorf("无法计算表达式: %v", err)
				continue
			}

			triggerStatus, ok := result.(bool)
			if !ok {
				log.Errorf("表达式计算结果非布尔值: %v", result)
				continue
			}
			timestamp := strconv.FormatInt(time.Now().Unix(), 10)

			alertData := AlertData{
				Alert:       alertName,
				Annotations: rule.Annotation,
				Rule:        rule.MatchField,
				Status:      triggerStatus,
				LastNotice:  timestamp,
			}

			if triggerStatus {
				alertData.FiringTime = timestamp
			} else {
				alertData.ResolveTime = timestamp
			}

			log.Debugf("监控项: %s, 计算结果: %v", alertName, triggerStatus)
			// 通知
			go MsgDestinationSelection(alertData)
			// 插入历史告警
			go HandleAlertStatus(alertData, triggerStatus)
		}
	}

	if !found {
		log.Warnf("未找到规则: %s", alertName)
	}
}

// EsSearch 根据条件检索ES并生成查询语句
func EsSearch(MatchField map[string]string, index string, timeRange int) (map[string]int64, error) {
	matchFieldMap := make(map[string]int64)
	esConnect, err := EsConnect()
	if err != nil {
		return nil, err
	}
	timeRange = -timeRange
	end := time.Now().UTC()
	start := end.Add(time.Duration(timeRange) * time.Minute).UTC()

	boolQuery := elastic.NewBoolQuery()
	rangeQuery := elastic.NewRangeQuery("@timestamp").Gte(start).Lte(end)
	boolQuery = boolQuery.Filter(rangeQuery)

	queryString := ""
	for _, value := range MatchField {
		if queryString != "" {
			queryString += " AND "
		}
		queryString += fmt.Sprintf("%s", value)
	}

	boolQuery = boolQuery.Must(elastic.NewQueryStringQuery(queryString))

	searchResult, err := esConnect.Search(index).
		Query(boolQuery).
		Pretty(true).
		Size(0). // Only need total hits, no documents
		Do(context.Background())
	if err != nil {
		log.Debugf("查询失败: %v", err)
		return matchFieldMap, err
	}

	totalHits := searchResult.TotalHits()
	log.Debugf("匹配字段 总命中数: %d", totalHits)

	for alias := range MatchField {
		matchFieldMap[alias] = totalHits
	}

	// 生成Kibana查询语句
	kibanaQuery := fmt.Sprintf(`{"query":{"bool":{"must":[{"range":{"@timestamp":{"gte":"%s","lte":"%s",
		"format":"strict_date_optional_time||epoch_millis"}}},{"query_string":{"query":"%s"}}]}}}`,
		start.Format(time.RFC3339Nano), end.Format(time.RFC3339Nano), queryString)

	log.Debugf("Kibana Query: %s", kibanaQuery)

	return matchFieldMap, nil
}

// EsIndexInit 索引初始化
func EsIndexInit() {
	ctx := context.Background()
	esConnect, err := EsConnect()
	if err != nil {
		log.Fatal(err)
	}
	indexName, err := GetConfig("default", "indexName")
	if err != nil {
		log.Fatal(err)
	}
	exists, err := esConnect.IndexExists(indexName).Do(ctx)
	if err != nil {
		log.Fatal(err)
	}
	// 索引映射
	body := `{
		"settings": {
			"number_of_shards": 1
		},
		"mappings": {
			"properties": {
				"FiringTime": {
					"type": "date",
					"fields": {
						"keyword": {
							"type": "keyword"
						}
					}
				},
				"ResolveTime": {
					"type": "date",
					"fields": {
						"keyword": {
							"type": "keyword"
						}
					}
				},
				"Alert": {
					"type": "keyword"
				},
				"Annotations": {
					"type": "text"
				},
				"Rule": {
					"type": "object"
				},
				"Status": {
					"type": "boolean"
				},
				"LastNotice": {
					"type": "date",
					"fields": {
						"keyword": {
							"type": "keyword"
						}
					}
				}
			}
		}
	}`
	if !exists {
		_, err := esConnect.CreateIndex(indexName).BodyString(body).Do(ctx)
		if err != nil {
			log.Fatalf("创建索引失败: %v", err)
		}
		log.Debugf("创建索引: %s", indexName)
	}
}

// EsConnect 创建es连接
func EsConnect() (*elastic.Client, error) {
	host, err := GetConfig("es", "host")
	if err != nil {
		return nil, err
	}
	username, err := GetConfig("es", "username")
	if err != nil {
		return nil, err
	}
	password, err := GetConfig("es", "password")
	if err != nil {
		return nil, err
	}
	options := []elastic.ClientOptionFunc{elastic.SetURL(host), elastic.SetScheme("https"), elastic.SetSniff(false)}
	if username != "" && password != "" {
		options = append(options, elastic.SetBasicAuth(username, password))
	}
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	httpClient := &http.Client{Transport: tr}
	options = append(options, elastic.SetHttpClient(httpClient))

	client, err := elastic.NewClient(options...)
	if err != nil {
		return nil, fmt.Errorf("创建Elasticsearch客户端失败: %v", err)
	}

	return client, nil
}

// loadActiveAlertsToCache 加载历史告警缓存
func loadActiveAlertsToCache() {
	ctx := context.Background()
	esConnect, err := EsConnect()
	if err != nil {
		log.Errorf("无法连接到 Elasticsearch: %v", err)
		return
	}

	indexName, err := GetConfig("default", "indexName")
	if err != nil {
		log.Errorf("无法获取索引名称配置: %v", err)
		return
	}

	boolQuery := elastic.NewBoolQuery().Must(
		elastic.NewTermQuery("Status", true),
	)

	searchResult, err := esConnect.Search().
		Index(indexName).
		Query(boolQuery).
		Size(1000).
		Do(ctx)
	if err != nil {
		log.Errorf("搜索失败: %v", err)
		return
	}

	for _, hit := range searchResult.Hits.Hits {
		var source map[string]interface{}
		err := json.Unmarshal(hit.Source, &source)
		if err != nil {
			log.Errorf("解析文档失败: %v", err)
			continue
		}
		alert := source["Alert"].(string)
		activeAlertsCache[alert] = true
	}
}

// HandleAlertStatus 历史告警状态调整
func HandleAlertStatus(alertData AlertData, isFiring bool) {
	ctx := context.Background()
	esConnect, err := EsConnect()
	if err != nil {
		log.Errorf("无法连接到 Elasticsearch: %v", err)
		return
	}

	indexName, err := GetConfig("default", "indexName")
	if err != nil {
		log.Errorf("无法获取索引名称配置: %v", err)
		return
	}

	if isFiring {
		if activeAlertsCache[alertData.Alert] {
			log.Infof("告警已存在，忽略重复触发: %s", alertData.Alert)
			return
		}

		docID, err := InsertDocument(esConnect, indexName, alertData)
		if err != nil {
			log.Errorf("插入文档失败: %v", err)
		} else {
			log.Infof("告警已保存: %s, 文档ID: %s", alertData.Alert, docID)
			activeAlertsCache[alertData.Alert] = true
		}
	} else {
		boolQuery := elastic.NewBoolQuery().Must(
			elastic.NewTermQuery("Alert.keyword", alertData.Alert),
			elastic.NewTermQuery("Status", true),
		)

		searchResult, err := esConnect.Search().
			Index(indexName).
			Query(boolQuery).
			Size(1).
			Do(ctx)
		if err != nil {
			log.Errorf("搜索失败: %v", err)
			return
		}

		if searchResult.Hits.TotalHits.Value > 0 {
			hit := searchResult.Hits.Hits[0]
			currentVersion := *hit.Version

			updateScript := `
				ctx._source.Status = false;
				ctx._source.ResolveTime = params.ResolveTime;
				ctx._source.LastNotice = params.LastNotice;
			`
			params := map[string]interface{}{
				"ResolveTime": alertData.ResolveTime,
				"LastNotice":  alertData.LastNotice,
			}
			updateRequest := esConnect.Update().
				Index(indexName).
				Id(hit.Id).
				Version(currentVersion).
				Script(elastic.NewScript(updateScript).Params(params))

			updateResponse, err := updateRequest.Do(ctx)
			if err != nil {
				log.Errorf("更新文档失败: %v", err)
			} else if updateResponse.Result != "updated" {
				log.Errorf("文档未成功更新: %v", updateResponse.Result)
			} else {
				log.Infof("告警状态更新为已恢复: %s", alertData.Alert)
				delete(activeAlertsCache, alertData.Alert)
			}
		} else {
			log.Infof("未找到活跃告警，跳过恢复状态更新: %s", alertData.Alert)
		}
	}
}

// InsertDocument 历史告警插入
func InsertDocument(esConnect *elastic.Client, indexName string, alertData AlertData) (string, error) {
	put1, err := esConnect.Index().
		Index(indexName).
		BodyJson(alertData).
		Do(context.Background())
	if err != nil {
		return "", err
	}
	return put1.Id, nil
}
